package com.imis.module.api.bus;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.MailAccount;
import cn.hutool.extra.mail.MailUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.imis.base.annotation.VerificationCaptcha;
import com.imis.base.constant.CommonConstant;
import com.imis.base.constant.HttpHeadersConstants;
import com.imis.base.constant.enums.ArgumentResponseEnum;
import com.imis.base.constant.enums.VerificationCodeTypeEnum;
import com.imis.base.globle.response.BaseResponse;
import com.imis.base.globle.response.CommonResponse;
import com.imis.base.util.*;
import com.imis.module.api.model.ro.SendEmailDTO;
import com.imis.module.api.model.ro.SendSmsDTO;
import com.imis.module.api.model.vo.VerificationCodeVO;
import com.imis.module.base.BaseBus;
import com.imis.module.system.model.converter.SysFileConverter;
import com.imis.module.system.model.po.SysFile;
import com.imis.module.system.model.vo.SysFileVO;
import com.imis.module.system.service.ISysFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * CommonBus<br>
 * 公共 业务处理类
 * </p>
 *
 * @author XinLau
 * @version 1.0
 * @since 2020年03月26日 11:18
 */
@Slf4j
@Service
public class CommonBus extends BaseBus {

    /**
     * 文件存放 服务类
     */
    private ISysFileService iSysFileService;

    @Autowired
    public void setSysFileService(ISysFileService iSysFileService) {
        this.iSysFileService = iSysFileService;
    }

    /**
     * 验证码操作类
     */
    private KaptchaVerificationUtil kaptchaVerificationUtil;

    @Autowired
    public void setKaptchaVerificationUtil(KaptchaVerificationUtil kaptchaVerificationUtil) {
        this.kaptchaVerificationUtil = kaptchaVerificationUtil;
    }

    /**
     * JSON文件路径
     */
    @Value(value = "${imis-boot.path.jsonPath}")
    private String jsonPath;

    /**
     * 文件本地存储路径
     */
    @Value(value = "${imis-boot.path.upload}")
    private String uploadPath;

    /**
     * 文件存储类型（本地：local）
     */
    @Value(value = "${imis-boot.uploadType}")
    private String uploadType;

    /**
     * SMTP服务器域名
     */
    @Value(value = "${imis-boot.email.host}")
    private String host;
    /**
     * SMTP服务端口
     */
    @Value(value = "${imis-boot.email.port}")
    private Integer port;
    /**
     * 是否需要用户名密码验证
     */
    @Value(value = "${imis-boot.email.isAuth}")
    private Boolean isAuth;
    /**
     * 发送方，遵循RFC-822标准
     */
    @Value(value = "${imis-boot.email.from}")
    private String from;
    /**
     * 用户名
     */
    @Value(value = "${imis-boot.email.user}")
    private String user;
    /**
     * 密码
     */
    @Value(value = "${imis-boot.email.pass}")
    private String pass;

    /**
     * 读取JSON格式文件
     *
     * @param jsonPath - JSON文件路径
     * @return String -
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/7/23 8:56
     */
    private String readJson(String jsonPath) {
        // 默认UTF-8编码，可以在构造中传入第二个参数做为编码
        cn.hutool.core.io.file.FileReader fileReader = new cn.hutool.core.io.file.FileReader(jsonPath);
        return fileReader.readString().replaceAll("\\r\\n", StringPool.EMPTY);
    }

    /**
     * 文件字节本地保存
     *
     * @param filePath           - 文件路径
     * @param fileName           - 文件名称
     * @param multipartFileBytes - 文件字节
     * @return null -
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/7/27 13:54
     */
    private Boolean doFileUploadForLocal(final String filePath, final String fileName, final byte[] multipartFileBytes) {
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                // 创建文件根目录
                file.mkdirs();
            }
            String savePath = file.getPath() + File.separator + fileName;
            File saveFile = new File(savePath);
            FileCopyUtils.copy(multipartFileBytes, saveFile);
            return Boolean.TRUE;
        } catch (IOException e) {
            log.error(e.getMessage());
            return Boolean.FALSE;
        }
    }

    /**
     * 获取验证码
     *
     * @param type - 验证码类型
     * @return VerificationCodeVO - 验证码返回值 对象
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/30 12:01
     */
    public CommonResponse<VerificationCodeVO> getVerificationCode(final String type) {
        VerificationCodeVO verificationCode = this.kaptchaVerificationUtil.getVerificationCode(type);
        ArgumentResponseEnum.VERIFICATION_CODE_ERR.assertNotNull(verificationCode);
        return new CommonResponse<>(verificationCode);
    }

    /**
     * 获取短信验证码
     *
     * @param sendSms - 手机短信发送对象
     * @return Result -
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/30 12:01
     */
    @VerificationCaptcha(type = VerificationCodeTypeEnum.OPERATION, captcha = "#sendSms.captcha", verificationCodeIdentification = "#sendSms.verificationCodeIdentification")
    public BaseResponse sendSms(final SendSmsDTO sendSms) {
        String phone = sendSms.getPhone();
        // 缓存的Key
        String key = CommonConstant.KAPTCHA_SESSION_KEY + CommonConstant.UNDERSCORE + VerificationCodeTypeEnum.SMS + CommonConstant.UNDERSCORE + phone;
        // 1.验证用户五分钟发一次有效的手机验证码
        Object redisCaptcha = this.redisUtil.get(key);
        ArgumentResponseEnum.SEND_SMS_ERR_REPEAT.assertNotEmpty((String) redisCaptcha, phone);
        // 参数名
        String[] paramName = {"code"};
        // 随机数（验证码）
        String smsCaptcha = ConvertUtils.randomNumbers(6);
        // 参数值
        String[] paramValue = {smsCaptcha};
        // 2.发送短信
        com.aliyuncs.CommonResponse commonResponse = SendSmsUtil.doSendSms(SendSmsEnum.LOGIN_TEMPLATE_CODE, phone, new SendSmsUtil.TemplateParam(paramName, paramValue));
        ArgumentResponseEnum.SEND_SMS_ERR.assertNotNull(commonResponse);
        JSONObject jsonObject = JSONUtil.parseObj(commonResponse.getData());
        log.debug(commonResponse.getData());
        String code = jsonObject.get("Code", String.class);
        ArgumentResponseEnum.SEND_SMS_ERR_API.assertIsTrue(CommonConstant.OK.equals(code), commonResponse.getData());
        // 3.添加缓存 带过期时间（包括0 - 立即过期）
        boolean set = this.redisUtil.set(key, smsCaptcha, CommonConstant.SME_KAPTCHA_SESSION_EFFECTIVE_TIME);
        ArgumentResponseEnum.SEND_SMS_ERR.assertIsTrue(set);
        return new BaseResponse();
    }

    /**
     * 获取邮箱验证码
     *
     * @param sendEmail - 邮箱
     * @return Result -
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/30 12:01
     */
    @VerificationCaptcha(type = VerificationCodeTypeEnum.OPERATION, captcha = "#sendEmail.captcha", verificationCodeIdentification = "#sendEmail.verificationCodeIdentification")
    public BaseResponse sendEmail(final SendEmailDTO sendEmail) {
        // 1.随机数（验证码）
        String emailCaptcha = ConvertUtils.randomNumbers(6);
        // 2.发送邮件参数设置
        MailAccount account = new MailAccount();
        account.setHost(host);
        account.setPort(port);
        account.setAuth(isAuth);
        account.setFrom(from);
        account.setUser(user);
        account.setPass(pass);
        // 3.邮箱模板
        String templateContentStr = "<p>尊敬的 {email}，您好！</p>" +
                "<p><h2>感谢您对平台的支持。</h2></p>" +
                "<p>您的邮箱验证码： <h4>{code}</h4></p>" +
                "<p>(该验证码5分钟内有效。该邮件由系统自动发送，请勿直接回复)</p>";
        // 4.填充模板参数
        Map<String, String> paramsMap = new HashMap<>(2);
        paramsMap.put("email", sendEmail.getEmail());
        paramsMap.put("code", emailCaptcha);
        String content = StrUtil.format(templateContentStr, paramsMap);
        // 5.发送邮件
        MailUtil.send(account, CollUtil.newArrayList(sendEmail.getEmail()), "邮箱验证码", content, true);
        // 6.添加缓存 带过期时间（包括0 - 立即过期）
        String key = CommonConstant.KAPTCHA_SESSION_KEY + CommonConstant.UNDERSCORE + VerificationCodeTypeEnum.EMAIL + CommonConstant.UNDERSCORE + sendEmail.getEmail();
        boolean set = this.redisUtil.set(key, emailCaptcha, CommonConstant.EMAIL_KAPTCHA_SESSION_EFFECTIVE_TIME);
        ArgumentResponseEnum.SEND_EMAIL_ERR.assertIsTrue(set);
        return new BaseResponse();
    }

    /**
     * 获取JSON文件内容
     *
     * @param fileName - JSON文件名称
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/7/23 9:00
     */
    public CommonResponse<String> getJsonData(final String fileName) {
        String jsonFilePath = jsonPath + fileName + ".json";
        return new CommonResponse<>(this.readJson(jsonFilePath));
    }

    /**
     * 多文件上传
     *
     * @param multipartHttpServletRequest - MultipartHttpServletRequest
     * @return CommonResponse<List<FileVO>>
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/10 15:32
     */
    public CommonResponse<List<SysFileVO>> doFileUpload(final MultipartHttpServletRequest multipartHttpServletRequest) {
        // 1.编码格式
        String characterEncoding = multipartHttpServletRequest.getCharacterEncoding();
        log.debug("characterEncoding_" + characterEncoding);
        // 2.requestParameterMap 请求参数 - 文字
        Map<String, String[]> requestParameterMap = multipartHttpServletRequest.getParameterMap();
        for (Map.Entry<String, String[]> parameterMap : requestParameterMap.entrySet()) {
            // 参数值Key
            String parameterKey = parameterMap.getKey();
            // 参数值数组
            String[] valueArray = parameterMap.getValue();
            log.debug("parameterKey_ {} : {}", parameterKey, JSONUtil.toJsonStr(valueArray));
        }
        // 文件保存对象
        List<SysFile> sysFileList = new ArrayList<>();
        // 3.requestMultiFileMap 请求参数 - 文件
        MultiValueMap<String, MultipartFile> requestMultiFileMap = multipartHttpServletRequest.getMultiFileMap();
        for (Map.Entry<String, List<MultipartFile>> multiFileMap : requestMultiFileMap.entrySet()) {
            // 参数Key
            String multiFileKey = multiFileMap.getKey();
            log.debug("multiFileKey_ {}", multiFileKey);
            // 参数Key对应的文件集合
            List<MultipartFile> multipartFileList = multiFileMap.getValue();
            for (MultipartFile multipartFile : multipartFileList) {
                //  文件非空
                if (!multipartFile.isEmpty()) {
                    //  参数Key
                    String name = multipartFile.getName();
                    //  文件全名称
                    String originalFilename = multipartFile.getOriginalFilename();
                    //  4.文件对象
                    SysFile sysFile = new SysFile();
                    sysFile.setFileType(multipartFile.getContentType());
                    sysFile.setFileSize(multipartFile.getSize());
                    sysFile.setDelFlag(0);
                    //  文件存放名 UUID 防止重复文件名
                    String uuid = IdWorker.get32UUID();
                    //  文件后缀名（带“.”）
                    String ext = originalFilename.substring(originalFilename.lastIndexOf(CommonConstant.DOT));
                    String newName = uuid + ext;
                    sysFile.setFileName(newName);
                    sysFile.setRealName(originalFilename);
                    sysFile.setDescription(CommonConstant.EMPTY);
                    sysFile.setFileUrl(uploadPath + CommonConstant.SLASH + newName);
                    try {
                        //   5.文件字节内容
                        byte[] multipartFileBytes = multipartFile.getBytes();
                        if (CommonConstant.UPLOAD_TYPE_LOCAL.equals(uploadType)) {
                            // 文件本地保存
                            boolean upload = this.doFileUploadForLocal(uploadPath, newName, multipartFileBytes);
                            ArgumentResponseEnum.FILE_ADD_ERR.assertIsTrue(upload, originalFilename);
                        }
                    } catch (IOException e) {
                        ArgumentResponseEnum.FILE_ADD_ERR.assertFail(originalFilename);
                    }
                    sysFileList.add(sysFile);
                }
            }
        }
        // 6.将文件记录保存到数据库
        boolean saveBatch = this.iSysFileService.saveBatchFile(sysFileList);
        ArgumentResponseEnum.FILE_ADD_ERR.assertIsTrue(saveBatch);
        // 7.保存成功，回显给前端
        return new CommonResponse<>(SysFileConverter.INSTANCE.getReturnValue(sysFileList));
    }

    /**
     * 跨服务器文件下载
     *
     * @param fileId - 文件编号
     * @return BaseResponse
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/10 15:32
     */
    public BaseResponse doFileDownload(final Long fileId) {
        // 1.fileId 合法性判断
        ArgumentResponseEnum.FILE_DOWNLOAD_ERR.assertNotNull(fileId);
        // 2.获取文件信息
        SysFile sysFile = this.iSysFileService.getById(fileId);
        ArgumentResponseEnum.FILE_DOWNLOAD_ERR.assertNotNull(sysFile);
        // 3.获取并设置 HttpServletResponse 强制下载不打开
        HttpServletResponse httpServletResponse = SpringContextUtils.getHttpServletResponse();
        httpServletResponse.setContentType("application/force-download");
        httpServletResponse.setHeader(HttpHeadersConstants.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
        httpServletResponse.setHeader(HttpHeadersConstants.CONTENT_DISPOSITION, "attachment;filename=" + sysFile.getFileName());
        try {
            // 4.根据文件地址创建URL
            URL url = new URL(sysFile.getFileUrl());
            // 5.获取此路径的连接
            URLConnection urlConnection = url.openConnection();
            // 6.获取文件大小，并设置给Response
            Long fileLength = urlConnection.getContentLengthLong();
            httpServletResponse.setHeader(HttpHeadersConstants.CONTENT_LENGTH, String.valueOf(fileLength));
            // 7.设置缓冲区大小
            byte[] buffer = new byte[1024];
            // 8.初始化缓冲输入流；  获取Response输出流；   初始化缓冲输出流
            try (BufferedInputStream bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());
                 OutputStream httpServletResponseOutputStream = httpServletResponse.getOutputStream();
                 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(httpServletResponseOutputStream)
            ) {
                int i = bufferedInputStream.read(buffer);
                //  9.每次读取缓存大小的流，写到输出流
                while (i != -1) {
                    bufferedOutputStream.write(buffer, 0, i);
                    i = bufferedInputStream.read(buffer);
                }
                //  10.将所有的读取的流返回给客户端
                httpServletResponse.flushBuffer();
            }
        } catch (IOException e) {
            ArgumentResponseEnum.FILE_DOWNLOAD_ERR_IO.assertFail(e);
        }
        return new BaseResponse();
    }

    /**
     * 在线预览
     *
     * @param fileId - 文件编号
     * @return Result
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/10 15:32
     */
    public void doOnlinePreview(final Long fileId) throws IOException {
        // 1.fileId 合法性判断
        ArgumentResponseEnum.FILE_ONLINE_PREVIEW_ERR.assertNotNull(fileId);
        // 2.获取文件信息
        SysFile sysFile = this.iSysFileService.getById(fileId);
        ArgumentResponseEnum.FILE_ONLINE_PREVIEW_ERR.assertNotNull(sysFile);
        // 3.获取并设置 HttpServletResponse
        HttpServletResponse httpServletResponse = SpringContextUtils.getHttpServletResponse();
        httpServletResponse.setContentType(MediaType.APPLICATION_PDF_VALUE);
        httpServletResponse.setHeader(HttpHeadersConstants.CONTENT_TYPE, MediaType.APPLICATION_PDF_VALUE);
        // 4.根据文件地址创建URL
        ArgumentResponseEnum.FILE_ONLINE_PREVIEW_ERR.assertNotEmpty(sysFile.getFileUrl());
        URL url = new URL(sysFile.getFileUrl());
        // 5.获取此路径的连接
        URLConnection urlConnection = url.openConnection();
        // 6.获取文件大小，并设置给Response
        Long fileLength = urlConnection.getContentLengthLong();
        httpServletResponse.setHeader(HttpHeadersConstants.CONTENT_LENGTH, String.valueOf(fileLength));
        // 7.设置缓冲区大小
        byte[] buffer = new byte[1024];
        // 8.初始化缓冲输入流；  获取Response输出流；   初始化缓冲输出流
        try (BufferedInputStream bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());
             OutputStream httpServletResponseOutputStream = httpServletResponse.getOutputStream();
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(httpServletResponseOutputStream)
        ) {
            int i = bufferedInputStream.read(buffer);
            // 9.每次读取缓存大小的流，写到输出流
            while (i != -1) {
                bufferedOutputStream.write(buffer, 0, i);
                i = bufferedInputStream.read(buffer);
            }
            // 10.将所有的读取的流返回给客户端
            httpServletResponse.flushBuffer();
        }
    }

}
